﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interpreter.BuiltIns
{
	internal class GameWindow : Module
	{
		public override void Register(FunctionRegistry registry)
		{
			registry.Register(21, new GameWindowPopEvent());
			registry.Register(22, new GameWindowCtor());
			registry.Register(23, new GameWindowStart());
			registry.Register(25, new GameWindowBlitScreen());
			registry.Register(55, new GameWindowClose());
		}

		private class GameWindowClose : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				GameCore.GameInstance game = args[0].Value0 as GameCore.GameInstance;
				game.EndGame();
				return Value.Null;
			}
		}

		private class GameWindowBlitScreen : NativeFunction
		{
			private static bool TryExtractColor(Value listOfColorsMaybe, int[] colorOut, bool throwUponFailure)
			{
				Value[] listValue = (Value[])listOfColorsMaybe.Value0;
				colorOut[3] = 255;
				int length = (int)listOfColorsMaybe.Value1;
				if (length == 3 || length == 4)
				{
					for (int i = 0; i < length; ++i)
					{
						if (listValue[i].Type == Value.TYPE_INTEGER)
						{
							colorOut[i] = (int)listValue[i].Value0 & 255;
						}
						else if (listValue[i].Type == Value.TYPE_FLOAT)
						{
							colorOut[i] = (int)((double)listValue[i].Value0 * 255) & 255;
						}
						else
						{
							if (throwUponFailure)
							{
								throw new Exception("Color values must be floats or ints.");
							}
							return false;
						}
					}
					
					return true;
				}

				if (throwUponFailure)
				{
					throw new Exception("Colors must be lists of 3 or 4 integers or floats.");
				}
				return false;
			}

			public override Value Do(StackContext stackContext, Value[] args)
			{
				GameSceneBaseImpl scene = (GameSceneBaseImpl)args[0].Value1;
				GameCore.Screen screen = scene.Screen;
				Value list = args[1];
				Value[] blits = (Value[])list.Value0;
				Value[] polygonPoints;
				Value polygonPointList, value1, value2;
				int length = (int)list.Value1;
				int i = 0;
				int[] colorOut = new int[4];
				int drawCommand, x, y, width, height, borderWidth, t;
				string textString;
				GameCore.Image image;
				while (i < length)
				{
					drawCommand = (int)blits[i++].Value0;
					switch (drawCommand)
					{
						case 1: // blit image
							image = (GameCore.Image)blits[i++].Value0;
							x = (int)blits[i++].Value0;
							y = (int)blits[i++].Value0;
							screen.Blit(image, x, y);
							break;

						case 2: // filled rectangle
						case 3: // border rectangle
							if (drawCommand == 3)
							{
								borderWidth = (int)blits[i++].Value0;
							}
							TryExtractColor(blits[i++], colorOut, true);
							x = (int)blits[i++].Value0;
							y = (int)blits[i++].Value0;
							width = (int)blits[i++].Value0;
							height = (int)blits[i++].Value0;
							if (drawCommand == 2)
							{
								// TODO: create a game core function that takes in the integers instead of a new Color instance. 
								GameCore.Draw.Rectangle(screen, colorOut[0], colorOut[1], colorOut[2], colorOut[3], x, y, width, height);
							}
							else
							{
								throw new NotImplementedException("TODO: border rectangle drawing.");
							}
							break;

						case 4: // filled ellipse
						case 5: // border ellipse
							if (drawCommand == 5)
							{
								borderWidth = (int)blits[i++].Value0;
							}
							TryExtractColor(blits[i++], colorOut, true);
							x = (int)blits[i++].Value0;
							y = (int)blits[i++].Value0;
							width = (int)blits[i++].Value0;
							height = (int)blits[i++].Value0;

							if (drawCommand == 4)
							{
								GameCore.Draw.Ellipse(screen, colorOut[0], colorOut[1], colorOut[2], colorOut[3], x, y, width, height);
							}
							else
							{
								// TODO: border only
							}
							break;

						case 6: // filled circle
						case 7: // border circle
							if (drawCommand == 7)
							{
								borderWidth = (int)blits[i++].Value0;
							}
							TryExtractColor(blits[i++], colorOut, true);
							x = (int)blits[i++].Value0;
							y = (int)blits[i++].Value0;
							width = (int)blits[i++].Value0;
							if (drawCommand == 6)
							{
								GameCore.Draw.Circle(screen, colorOut[0], colorOut[1], colorOut[2], colorOut[3], x, y, width);
							}
							else
							{
								// TODO: border only
							}
							break;

						case 8:
							TryExtractColor(blits[i++], colorOut, true);
							polygonPointList = blits[i++];
							t = (int)polygonPointList.Value1; // length
							polygonPoints = (Value[])polygonPointList.Value0;
							int[] xs = new int[t];
							int[] ys = new int[t];
							for (x = t - 1; x >= 0; --x)
							{
								value1 = ((Value[])polygonPoints[x].Value0)[0];
								value2 = ((Value[])polygonPoints[x].Value0)[1];

								xs[x] = value1.Type == Value.TYPE_INTEGER ? (int)value1.Value0 : (int)(double)value1.Value0;
								ys[x] = value2.Type == Value.TYPE_INTEGER ? (int)value2.Value0 : (int)(double)value2.Value0;
							}
							GameCore.Draw.Polygon(screen, colorOut[0], colorOut[1], colorOut[2], colorOut[3], xs, ys);
							break;

						case 9:
							TryExtractColor(blits[i++], colorOut, true);
							textString = (string)blits[i++].Value0;
							x = (int)blits[i++].Value0;
							y = (int)blits[i++].Value0;
							screen.AddText(GameCore.Font.Helvetica20, textString, x, y, colorOut[0], colorOut[1], colorOut[2], colorOut[3]);
							break;

					}
				}
				return Value.Null;
			}
		}

		private class GameWindowStart : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value gameObject = args[0];
				Value updateMethod = args[1];
				Value renderMethod = args[2];
				GameCore.GameInstance gameInstance = gameObject.Value0 as GameCore.GameInstance;
				gameInstance.SetTitle("B Flat Window");
				GameSceneBaseImpl gameScene = gameObject.Value1 as GameSceneBaseImpl;

				gameScene.SetMethods(stackContext, updateMethod, renderMethod);

				gameInstance.StartGame(gameScene);

				return gameScene.ExceptionToBubble ?? Value.Null;
			}
		}

		private class GameWindowPopEvent : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				GameSceneBaseImpl scene = args[0].Value1 as GameSceneBaseImpl;
				GameCore.GameEvent ev = scene.PopEvent();
				if (ev != null)
				{
					if (ev.Type == GameCore.EventType.KeyDown || ev.Type == GameCore.EventType.KeyUp)
					{
						return new Value(Value.TYPE_LIST, new Value[]
						{
							Value.GetInteger(1), // type (KEY)
							Value.GetInteger(KeyCodeLookup.GetKeyCode(ev.Key)), // key code
							ev.Type == GameCore.EventType.KeyDown ? Value.True : Value.False, // key down
							Value.Null, // mouse button
							Value.Null, // x
							Value.Null, // y
							Value.Null, // amount
						}, 7);
					}
					else if (ev.Type == GameCore.EventType.Quit)
					{
						return new Value(Value.TYPE_LIST, new Value[] {
							Value.GetInteger(5), // type (CLOSE_BUTTON)
							Value.Null,
							Value.True,
							Value.Null,
							Value.Null,
							Value.Null,
							Value.Null
						}, 7);
					}
				}

				return Value.Null;
			}
		}

		private class GameWindowCtor : NativeFunction
		{
			// args: int width, int height, int fps, bool fullscreen
			// output: [screen object, game instance, int width, int height]
			public override Value Do(StackContext stackContext, Value[] args)
			{
				int width = (int)stackContext.Args[0].Value0;
				int height = (int)stackContext.Args[1].Value0;
				int fps = (int)stackContext.Args[2].Value0;
				bool fullscreen = (bool)stackContext.Args[3].Value0;
				GameCore.GameInstance gi = new GameCore.GameInstance(width, height, fps, fullscreen);
				GameCore.GameSceneBase gsb = new GameSceneBaseImpl(gi);
				Value output = new Value(Value.TYPE_NATIVE_OBJECT, gi, gsb);
				gi.QuitByAltF4 = true;
				gi.QuitByEscape = false;
				return output;
			}
		}

		internal class GameSceneBaseImpl : GameCore.GameSceneBase
		{
			private StackContext callStack;
			private Value updateMethod;
			private Value renderMethod;
			private long microtime = 0;

			public Value ExceptionToBubble { get; private set; }

			public GameCore.Screen Screen { get; private set; }

			private GameCore.GameInstance gi;

			public GameSceneBaseImpl(GameCore.GameInstance gi)
			{
				this.gi = gi;
			}

			public void SetMethods(StackContext callStack, Value updateMethod, Value renderMethod)
			{
				this.callStack = callStack;
				this.updateMethod = updateMethod;
				this.renderMethod = renderMethod;
			}

			public GameCore.GameEvent PopEvent()
			{
				if (this.events.Count > 0)
				{
					return this.events.Dequeue();
				}
				return null;
			}

			private Queue<GameCore.GameEvent> events = new Queue<GameCore.GameEvent>();
			protected override void ProcessInput(GameCore.GameEvent[] events, GameCore.PressedKeysLookup pressedKeys, GameCore.MouseState mouseState)
			{
				foreach (GameCore.GameEvent e in events)
				{
					this.events.Enqueue(e);
				}
			}

			private long lastTime = 0;

			private List<double> last20fps = new List<double>() { 0 };

			protected override void Render(GameCore.Screen screen)
			{
				this.Screen = screen;
				this.ExceptionToBubble = this.CallMethod(this.renderMethod);

				if (this.ExceptionToBubble == null)
				{
					long now = DateTime.Now.Ticks;
					double seconds = (now - lastTime) / 10000000.0;
					double fps = 1.0 / seconds;
					fps = ((int)(fps * 1000)) / 1000.0;
					lastTime = now;

					last20fps.Add((int)fps);
					if (last20fps.Count > 20)
					{
						last20fps.RemoveAt(0);
					}

					double averageFps = 0;
					for (int i = 0; i < last20fps.Count; ++i)
					{
						averageFps += last20fps[i];
					}

					fps = averageFps / 20;
					fps = (int)(fps * 1000) / 1000.0;
					this.gi.SetTitle("Cycles: " + Interpreter.cycles + ", FPS: " + fps);
					Interpreter.cycles = 0;
				}
				else
				{
					this.gi.EndGame();
				}
			}

			protected override void Update()
			{
				long now = System.DateTime.Now.Ticks;
				long again = System.DateTime.Now.Ticks;
				long t = again - now;
				double diff = (now - microtime) / 10000000.0;
				this.microtime = now;
				this.ExceptionToBubble = this.CallMethod(this.updateMethod);
				if (this.ExceptionToBubble != null)
				{
					this.gi.EndGame();
				}
			}

			// Returns an exception
			private Value CallMethod(Value methodPointer)
			{
				MethodSignature method = methodPointer.Value0 as MethodSignature;
				Value context = methodPointer.Value1 as Value;
				StackContext stack = new StackContext(this.callStack, method.StartLocation, context, new Value[0], this.callStack.Metadata);
				stack.InterpreterYieldUponPop = true;
				Interpreter.Interpret(this.callStack.Metadata, stack);
				if (stack.SuddenQuit == 2)
				{
					// the stack's return value is hijacked and contains the exception that should be bubbled beyond the native transition.
					return stack.ReturnValue;
				}

				return null;
			}
		}
	}
}
